package cn.xuexiluxian.open.common.utils.easyexcel;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @Description EasyExcel工具类
 * @Author 王俊南
 * @Date 2022/6/2 1:58 下午
 **/
@Slf4j
public class EasyExcelUtils {

    private static final String FILE_SUFFIX = ".xlsx";

    //设置表头
    private static List<List<String>> head(String[] headMap) {
        List<List<String>> list = new ArrayList<List<String>>();

        for (String head : headMap) {
            List<String> headList = new ArrayList<String>();
            headList.add(head);
            list.add(headList);
        }
        return list;
    }

    //设置导出的数据内容
    private static List<List<Object>> dataList(List<Map<String, Object>> dataList, String[] dataStrMap) {
        List<List<Object>> list = new ArrayList<List<Object>>();
        for (Map<String, Object> map : dataList) {
            List<Object> data = new ArrayList<Object>();
            for (int i = 0; i < dataStrMap.length; i++) {
                data.add(map.get(dataStrMap[i]));
            }
            list.add(data);
        }
        return list;
    }


    /**
     * 创建即将导出的sheet页（sheet页中含有带下拉框的列）
     *
     * @param head      导出的表头信息和配置
     * @param sheetNo   sheet索引
     * @param sheetName sheet名称
     * @param <T>       泛型
     * @return sheet页
     */
    public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {
        Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head);

        return EasyExcel.writerSheet(sheetNo, sheetName)
                .head(head)
                .registerWriteHandler(new SelectedSheetWriteHandler(selectedMap))
                .build();
    }


    /**
     * 解析表头类中的下拉注解
     *
     * @param head 表头类
     * @param <T>  泛型
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();

        // getDeclaredFields(): 返回全部声明的属性；getFields(): 返回public类型的属性
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            // 解析注解信息
            ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (selected != null) {
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected);
                if (source != null && source.length > 0) {
                    excelSelectedResolve.setSource(source);
                    excelSelectedResolve.setFirstRow(selected.firstRow());
                    excelSelectedResolve.setLastRow(selected.lastRow());
                    if (property != null && property.index() >= 0) {
                        selectedMap.put(property.index(), excelSelectedResolve);
                    } else {
                        selectedMap.put(i, excelSelectedResolve);
                    }
                }
            }
        }

        return selectedMap;
    }

    /**
     * map导出
     *
     * @param response
     * @param data
     * @param heads
     * @throws IOException
     */
    public static void export(HttpServletResponse response, List data, String[] heads) throws IOException {
        setExportContenttype(response);
        // 这里需要设置不关闭流
        EasyExcel.write(response.getOutputStream()).head(head(heads)).sheet("sheet").doWrite(dataList(data, heads));
    }

    /**
     * @param response
     * @param clazz    类型
     * @throws IOException
     */
    public static void exportTemplate(HttpServletResponse response, Class clazz) throws IOException {
        exportTemplate(response, clazz, null, false);
    }

    /**
     * @param response
     * @param clazz    类型
     * @throws IOException
     */
    public static void exportSelectTemplate(HttpServletResponse response, Class clazz) throws IOException {
        exportSelectTemplate(response, clazz, null);
    }

    /**
     * @param response
     * @param clazz    类型
     * @param fileName 文件名称
     * @throws IOException
     */
    public static void exportSelectTemplate(HttpServletResponse response, Class clazz, String fileName) throws IOException {
        exportTemplate(response, clazz, fileName, true);
    }

    /**
     * @param response
     * @param clazz    类型
     * @param fileName 文件名称
     * @throws IOException
     */
    public static void exportTemplate(HttpServletResponse response, Class clazz, String fileName, boolean isSelectSheet) throws IOException {
        if (StrUtil.isBlank(fileName)) {
            fileName = IdUtil.fastSimpleUUID();
        }
        setExportContenttype(response, fileName);

        if (isSelectSheet) {
            WriteSheet writeSheet = writeSelectedSheet(clazz, 0, "sheet");
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
                    .excludeColumnFieldNames(Arrays.asList("errorMsg"))
                    .build();
            excelWriter.write(new ArrayList<>(), writeSheet).finish();
        } else {
            EasyExcel.write(response.getOutputStream(), clazz)
                    .excludeColumnFieldNames(Arrays.asList("errorMsg"))
                    .sheet("sheet").doWrite(new ArrayList<>());
        }
    }

    /**
     * @param response
     * @param data     数据
     * @param clazz    类型
     * @throws IOException
     */
    public static void export(HttpServletResponse response, List data, Class clazz) throws IOException {
        export(response, null, data, clazz);
    }

    /**
     * 导出excel
     *
     * @param response
     * @param fileName  文件名
     * @param sheetName sheet名
     * @param list      数据 list为空返回 空Excel
     */
    public static void export(HttpServletResponse response, String fileName, String sheetName, List<?> list, Class<?> zlass) throws IOException {
        setExportContenttype(response, fileName);
        EasyExcel.write(response.getOutputStream(), zlass)
                .sheet(sheetName).doWrite(list);
    }

    private static void setExportContenttype(HttpServletResponse response) throws UnsupportedEncodingException {
        setExportContenttype(response, null);
    }

    private static void setExportContenttype(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        if (StrUtil.isBlank(fileName)) {
            fileName = IdUtil.fastSimpleUUID();
        }
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        String fileNameCode = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename=" + fileNameCode + FILE_SUFFIX);
    }

    /**
     * excel导出 批注
     *
     * @param response
     * @param fileName     文件名
     * @param list         导出数据
     * @param writeHandler 批注拦截器
     */
    public static void export(HttpServletResponse response, String fileName,
                              List list, WriteHandler writeHandler, Class zlass) throws IOException {
        setExportContenttype(response, fileName);
        EasyExcel.write(response.getOutputStream(), zlass)
                .inMemory(Boolean.TRUE).registerWriteHandler(writeHandler)
                .sheet(fileName).doWrite(list);
    }


    /**
     * 导出Excel
     *
     * @param response
     * @param fileName 文件名
     * @param list     导出数据
     * @return void
     */
    public static void export(HttpServletResponse response, String fileName, List<?> list, Class<?> zlass) throws IOException {
        export(response, fileName, fileName, list, zlass);
    }

    /**
     * 读取excel文件
     *
     * @param file  文件
     * @param clazz 模板类
     * @return java.util.List
     */
    public static <T> List<T> read(MultipartFile file, Class<T> clazz) {
        try {
            return EasyExcel.read(new BufferedInputStream(file.getInputStream())).head(clazz).sheet()
                    .doReadSync();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static List<LinkedHashMap<Integer, Object>> readExcel(InputStream inputStream) {
        return EasyExcel.read(inputStream).headRowNumber(0).autoCloseStream(true).doReadAllSync();
    }

    public static List<LinkedHashMap<Integer, Object>> readExcel(InputStream inputStream, String sheetName) {
        return EasyExcel.read(inputStream).headRowNumber(0).autoCloseStream(true).sheet(sheetName).doReadSync();
    }

    public static List<LinkedHashMap<Integer, Object>> readExcel(InputStream inputStream, Integer sheetIndex) {
        return EasyExcel.read(inputStream).headRowNumber(0).autoCloseStream(true).sheet(sheetIndex).doReadSync();
    }

    public static Map<String, List<LinkedHashMap<Integer, Object>>> readNameExcel(InputStream inputStream, List<ReadSheet> readSheets) {
        SyncManySheetNameReadListener syncReadListener = new SyncManySheetNameReadListener();
        EasyExcel.read(inputStream).registerReadListener(syncReadListener).autoCloseStream(true)
                .build().read(readSheets).finish();
        return syncReadListener.getMap();
    }

    public static Map<Integer, List<LinkedHashMap<Integer, Object>>> readIndexExcel(InputStream inputStream, List<ReadSheet> readSheets) {
        SyncManySheetIndexReadListener syncReadListener = new SyncManySheetIndexReadListener();
        EasyExcel.read(inputStream).registerReadListener(syncReadListener).autoCloseStream(true)
                .build().read(readSheets).finish();
        return syncReadListener.getMap();
    }

    public static Map<String, List<LinkedHashMap<Integer, Object>>> readExcel(List<String> sheetNames, InputStream is) {
        Map<String, List<LinkedHashMap<Integer, Object>>> data;
        if (CollUtil.isEmpty(sheetNames)) {
            data = new HashMap<>();
            data.put("", readExcel(is));
        } else {
            List<ReadSheet> list = new ArrayList<>();
            for (String name : sheetNames) {
                list.add(new ReadSheet(null, name));
            }
            data = readNameExcel(is, list);
        }
        return data;
    }

    public static String getAsString(LinkedHashMap<Integer, Object> cell, int i) {
        Object o = cell.get(i);
        if (o == null) {
            return null;
        }
        return o.toString();
    }

    private static class SyncManySheetNameReadListener extends AnalysisEventListener<Object> {
        private Map<String, List<LinkedHashMap<Integer, Object>>> map = new HashMap<>();

        public SyncManySheetNameReadListener() {
        }

        @Override
        public void invoke(Object object, AnalysisContext context) {
            map.computeIfAbsent(context.readSheetHolder().getSheetName(),
                    a -> new ArrayList<>()).add((LinkedHashMap<Integer, Object>) object);
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
        }

        public Map<String, List<LinkedHashMap<Integer, Object>>> getMap() {
            return this.map;
        }

    }

    private static class SyncManySheetIndexReadListener extends AnalysisEventListener<Object> {
        private Map<Integer, List<LinkedHashMap<Integer, Object>>> map = new HashMap<>();

        public SyncManySheetIndexReadListener() {
        }

        @Override
        public void invoke(Object object, AnalysisContext context) {
            map.computeIfAbsent(context.readSheetHolder().getSheetNo(),
                    a -> new ArrayList<>()).add((LinkedHashMap<Integer, Object>) object);
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
        }

        public Map<Integer, List<LinkedHashMap<Integer, Object>>> getMap() {
            return this.map;
        }

    }

}

